import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:built_collection/built_collection.dart';
import 'package:dartz/dartz.dart';
import 'package:equatable/equatable.dart';
import 'package:http/http.dart' as http;
import 'package:website.core/core/domain/clients/api_client.dart';
import 'package:website.core/core/domain/config/config.dart';
import 'package:website.core/core/domain/entities/app_store/app_store_one_time_purchased_item.dart';
import 'package:website.core/core/domain/entities/app_store/app_store_purchased_item.dart';
import 'package:website.core/core/domain/entities/app_store/app_store_purchased_subscription.dart';
import 'package:website.core/core/domain/entities/app_store/app_store_purchased_subscription_status.dart';
import 'package:website.core/core/domain/entities/google_play/google_play_one_time_purchased_item.dart';
import 'package:website.core/core/domain/entities/google_play/google_play_purchased_item.dart';
import 'package:website.core/core/domain/entities/google_play/google_play_purchased_subscription.dart';
import 'package:website.core/core/domain/entities/google_play/google_play_purchased_subscription_pause_info.dart';
import 'package:website.core/core/domain/entities/google_play/google_play_purchased_subscription_status.dart';
import 'package:website.core/core/domain/entities/web/web_one_time_purchased_item.dart';
import 'package:website.core/core/domain/entities/web/web_purchased_item.dart';
import 'package:website.core/core/domain/entities/web/web_purchased_subscription.dart';
import 'package:website.core/core/domain/entities/web/web_purchased_subscription_status.dart';
import 'package:website.core/core/domain/errors/exceptions.dart';
import 'package:website.core/core/domain/repositories/purchased_item_repository/repository.dart';
import 'package:website.core/core/infrastructure/dtos/api/api_time_dto.dart';

part 'dtos/app_store_one_time_purchased_item_dto.dart';

part 'dtos/app_store_purchased_item_dto.dart';

part 'dtos/app_store_purchased_item_dto_factory.dart';

part 'dtos/app_store_purchased_subscription_dto.dart';

part 'dtos/app_store_purchased_subscription_status_dto.dart';

part 'dtos/google_play_one_time_purchased_item_dto.dart';

part 'dtos/google_play_purchased_item_dto.dart';

part 'dtos/google_play_purchased_item_dto_factory.dart';

part 'dtos/google_play_purchased_subscription_dto.dart';

part 'dtos/google_play_purchased_subscription_pause_info_dto.dart';

part 'dtos/google_play_purchased_subscription_status_dto.dart';

part 'dtos/order_placing_successful_response_dto.dart';

part 'dtos/purchased_item_getting_successful_response_dto.dart';

part 'dtos/web_one_time_purchased_item_dto.dart';

part 'dtos/web_purchased_item_dto.dart';

part 'dtos/web_purchased_item_dto_factory.dart';

part 'dtos/web_purchased_subscription_auto_renewal_enablement_successful_response_dto.dart';

part 'dtos/web_purchased_subscription_cancellation_successful_response_dto.dart';

part 'dtos/web_purchased_subscription_dto.dart';

part 'dtos/web_purchased_subscription_status_dto.dart';

class PurchasedItemRepositoryImpl implements PurchasedItemRepository {
  const PurchasedItemRepositoryImpl({
    required ApiClient apiClient,
  }) : _apiClient = apiClient;

  final ApiClient _apiClient;

  @override
  Future<Either<OrderPlacingFailure, OrderPlacingResult>> placeOrder({
    required String purchaseItemSku,
    required String returnUrl,
  }) async {
    final payload = {
      'purchase_item_sku': purchaseItemSku,
      'return_url': returnUrl,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/place-order');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on ServerException {
      const failure = ServerOrderPlacingFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkOrderPlacingFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkOrderPlacingFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedOrderPlacingFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerOrderPlacingFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto = _OrderPlacingSuccessfulResponseDto.fromJson(responseBody);

    final result = OrderPlacingResult(
      confirmationUrl: successfulResponseDto.confirmationUrl,
    );

    return right(result);
  }

  @override
  Future<Either<PurchasedItemGettingFailure, PurchasedItemGettingResult>>
      getPurchasedItems() async {
    final url = Uri.parse('${Config().apiBaseUrl}/obtain-purchased-items');

    final http.Response response;

    try {
      response = await _apiClient.post(url);
    } on ServerException {
      const failure = ServerPurchasedItemGettingFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkPurchasedItemGettingFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkPurchasedItemGettingFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedPurchasedItemGettingFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerPurchasedItemGettingFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto = _PurchasedItemGettingSuccessfulResponseDto.fromJson(responseBody);

    final appStorePurchasedItems =
        successfulResponseDto.appStorePurchasedItemDtos.map((appStorePurchasedItemDto) {
      return appStorePurchasedItemDto.toEntity();
    }).toBuiltList();

    final googlePlayPurchasedItems =
        successfulResponseDto.googlePlayPurchasedItemDtos.map((googlePlayPurchasedItemDto) {
      return googlePlayPurchasedItemDto.toEntity();
    }).toBuiltList();

    final webPurchasedItems = successfulResponseDto.webPurchasedItemDtos.map((webPurchasedItemDto) {
      return webPurchasedItemDto.toEntity();
    }).toBuiltList();

    final result = PurchasedItemGettingResult(
      appStorePurchasedItems: appStorePurchasedItems,
      googlePlayPurchasedItems: googlePlayPurchasedItems,
      webPurchasedItems: webPurchasedItems,
    );

    return right(result);
  }

  @override
  Future<Either<WebPurchasedSubscriptionCancellationFailure, WebPurchasedSubscription>>
      cancelWebPurchasedSubscription({
    required String originalTransactionId,
  }) async {
    final payload = {
      'original_transaction_id': originalTransactionId,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/cancel-web-purchased-subscription');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on ServerException {
      const failure = ServerWebPurchasedSubscriptionCancellationFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkWebPurchasedSubscriptionCancellationFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkWebPurchasedSubscriptionCancellationFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedWebPurchasedSubscriptionCancellationFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerWebPurchasedSubscriptionCancellationFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto =
        _WebPurchasedSubscriptionCancellationSuccessfulResponseDto.fromJson(responseBody);
    final purchasedSubscription = successfulResponseDto.purchasedSubscriptionDto.toEntity();

    return right(purchasedSubscription);
  }

  @override
  Future<Either<WebPurchasedSubscriptionAutoRenewalEnablementFailure, WebPurchasedSubscription>>
      enableWebPurchasedSubscriptionAutoRenewal({
    required String originalTransactionId,
  }) async {
    final payload = {
      'original_transaction_id': originalTransactionId,
    };

    final requestBody = jsonEncode(payload);

    final url = Uri.parse('${Config().apiBaseUrl}/enable-web-purchased-subscription-auto-renewal');

    const headers = {
      'Content-Type': 'application/json',
    };

    final http.Response response;

    try {
      response = await _apiClient.post(
        url,
        body: requestBody,
        headers: headers,
      );
    } on ServerException {
      const failure = ServerWebPurchasedSubscriptionAutoRenewalEnablementFailure();
      return left(failure);
    } on SocketException {
      const failure = NetworkWebPurchasedSubscriptionAutoRenewalEnablementFailure();
      return left(failure);
    } on TimeoutException {
      const failure = NetworkWebPurchasedSubscriptionAutoRenewalEnablementFailure();
      return left(failure);
    }

    if (response.statusCode == 401) {
      const failure = UnauthorizedWebPurchasedSubscriptionAutoRenewalEnablementFailure();
      return left(failure);
    }

    if (response.statusCode >= 500) {
      const failure = ServerWebPurchasedSubscriptionAutoRenewalEnablementFailure();
      return left(failure);
    }

    if (response.statusCode != 200) {
      throw UnimplementedError();
    }

    final responseBody = jsonDecode(response.body) as Map<String, dynamic>;
    final successfulResponseDto =
        _WebPurchasedSubscriptionAutoRenewalEnablementSuccessfulResponseDto.fromJson(responseBody);
    final purchasedSubscription = successfulResponseDto.purchasedSubscriptionDto.toEntity();

    return right(purchasedSubscription);
  }
}
